#include <connection.h>
#include <packet.h>
#include <ext/hash_map>
#include <iostream>


using namespace __gnu_cxx;
#define MULTIPLIER 1613
#define MAX_TABLE_SIZE 3343


struct connection_hash
{
    size_t operator ()(const connection& x) const 
    {
        return (size_t) ((x.addr1 + x.addr2 + MULTIPLIER * 
               (x.port1 + x.port2)) % MAX_TABLE_SIZE);
    }   

};

struct connection_equal 
{
    bool operator()(const connection& c1, 
                    const connection& c2) const
    {
        if (c1.addr1 == c2.addr1) {
            if (c1.addr2 != c2.addr2)
                return false;
        } else if (c1.addr1 == c2.addr2) {
            if (c1.addr2 != c2.addr1)
                return false;
        } else
            return false;

        // Till now equal as far as address is concerned
        
        if (c1.port1 == c2.port1) {
            if (c1.port2 != c2.port2)
                return false;
        } else if (c1.port1 == c2.port2) {
            if (c1.port2 != c2.port1)
                return false;
        } else 
           return false; 

        return true;
    }
};


typedef hash_map <connection, connection_state,
       connection_hash, connection_equal > ConHashMap;

typedef ConHashMap::value_type ConVal;

struct connectionPrinter
{
    void operator()(const ConVal& con) const 
    {
       struct in_addr sa = { con.first.addr1 };
       struct in_addr sd = { con.first.addr2 };
       std::cout << "  Connection " << std::endl;
       std::cout << "Addr1:" << inet_ntoa(sa) ;
       std::cout << "  Addr2:" 
           << inet_ntoa(sd) << std::endl;

       std::cout << "Port1:" << ntohs(con.first.port1) << "  Port2:" 
           << ntohs(con.first.port2) << std::endl;

       std::cout <<  "State: " << con.second << std::endl;
    }

};

Connections :: Connections() : _quer(0)
{
    informations = new ConHashMap;     

}

Connections :: ~ Connections() 
{
    delete (static_cast <ConHashMap *> (informations)) ;

}

int Connections::addConnectionInfo(Packet& pkt)
{

    unsigned int saddr, daddr, hlen;
    unsigned short sport, dport;

    ConHashMap *cncts = (static_cast <ConHashMap *>(informations));
    

#ifdef DEBUG_ME
        std::cerr << "The size is " << cncts->size() << std::endl;
#endif
    

#ifdef DEBUG_ME
    std::cerr << "Connections::addConnectionInfo \n";
#endif
    if (pkt.getPacketType() != IP_PACKET_TYPPE )
        return -1;

    ethernet_header *eh = (ethernet_header *) pkt._data;
    
    eh++;
    
    iphdr *ih = (iphdr *) eh;
#ifdef DEBUG_ME
    std::cerr << "Connections::addConnectionInfo 2 \n";
#endif
    
    std::cout << ih->protocol << std::endl;

    if (ih->protocol != IPPROTO_UDP  && ih->protocol != IPPROTO_TCP) 
        return -1;
#ifdef DEBUG_ME
    std::cerr << "Connections::addConnectionInfo 3 \n";
#endif

    saddr = ih->saddr;
    daddr = ih->daddr;  
    hlen = (ih->res_vers & 0xf) << 2;

    std::cerr << "Header len " << hlen << std::endl;
    if (ih->protocol == IPPROTO_UDP ) {

       udphdr *uh = (udphdr *)((char *)ih + hlen ); 
       sport = uh->sport;
       dport = uh->dport;  
       std::cout << "UDP PACKET  " << ntohs(uh->dport) 
           << "  " << ntohs(uh->sport) << std::endl;
       printAllAddedCons();

    } else if (ih->protocol == IPPROTO_TCP) {
       return -1;
       tcphdr *th = (tcphdr *)((char *)ih + hlen ); 
       sport = th->sport;
       dport = th->dport;       
    } else
       return -1; 

    connection new_con(saddr, daddr, sport, dport);

    ConHashMap::iterator it = cncts->find(new_con); 
   

    if (it != cncts->end()) {
#ifdef DEBUG_ME
        std::cerr << "The connection already exists \n";
        std::cerr << saddr << " " << daddr << " " << sport << " "
            << dport << std::endl;
#endif
        return -1;
    }

    cncts->insert(ConVal(new_con, TRYING_TO_CONNECT));

#ifdef DEBUG_ME
     if (cncts->size() == 10){
        printAllAddedCons();
        exit(1);
     }
#endif 
    return 0;
}

int Connections::addConnectionInfo(connection& new_con)
{
    ConHashMap *cncts = (static_cast <ConHashMap *>(informations));
    
    ConHashMap::iterator it = cncts->find(new_con); 
    
    if (it != cncts->end()) {
#ifdef DEBUG_ME
        std::cerr << "The connection already exists \n";
#endif
        return -1;
    }
    
    cncts->insert(ConVal(new_con, TRYING_TO_CONNECT));

    return 0;
}

connection_state Connections::getConnectionInfo(connection & con)
{

    ConHashMap *cncts = (static_cast <ConHashMap *>(informations));

    ConHashMap::iterator it = cncts->find(con); 
    
    if (it != cncts->end())
        return it->second;

    return CONNECTION_NOT_ADDED;
}

int Connections::deleteConnectionInfo(connection &con)
{

    ConHashMap *cncts = (static_cast <ConHashMap *>(informations));

    ConHashMap::iterator it = cncts->find(con); 
    
    if (it != cncts->end()){
        cncts->erase(it);
        return 0;
     }
    return -1;

}

void Connections::printAllAddedCons()
{
    
    ConHashMap *cncts = (static_cast <ConHashMap *>(informations));
    connectionPrinter printConns;
    
    for_each(cncts->begin(), cncts->end(), printConns); 

}

void Connections::addPacketQuer(PacketQuer *qr)
{
   if (!_quer)
    _quer = qr;
}


Packet* Connections::getNextPacket()
{
    if (!_quer)
        return NULL;
    return _quer->getPacket();

}
